﻿using Nest;
using Operation.Marketing.ElasticSearch.Query.BaseSpecifications;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Infrastructure.ElasticSearch.ParentChilds
{
    public class ConditionBuilder<T> where T : class
    {
        private List<ISearchSpecification<ISearchRequest<T>>> _queries;
        private bool _hasConditions = false;

        public ConditionBuilder()
        {
            _queries = new List<ISearchSpecification<ISearchRequest<T>>>();
        }

        public bool HasConditions => _hasConditions;

        public List<ISearchSpecification<ISearchRequest<T>>> Queries => _queries;

        public ConditionBuilder<T> IFAddQuery(bool bFlag, ISearchSpecification<ISearchRequest<T>> query)
        {
            if (bFlag)
                AddQuery(query);
            return this;
        }

        public ConditionBuilder<T> AddQuery(ISearchSpecification<ISearchRequest<T>> query)
        {
            if (query != null)
            {
                _queries.Add(query);
                _hasConditions = true;
            }

            return this;
        }

        public ConditionBuilder<T> IFAddQuerys(bool bFlag, List<ISearchSpecification<ISearchRequest<T>>> querys)
        {
            if (bFlag)
                AddQuerys(querys);
            return this;
        }

        public ConditionBuilder<T> AddQuerys(List<ISearchSpecification<ISearchRequest<T>>> querys)
        {
            if (querys.Any())
            {
                _queries.AddRange(querys);
                _hasConditions = true;
            }

            return this;
        }

        public ConditionBuilder<T> StringMatch(string value, Expression<Func<T, string>> field)
        {
            AddQuery(new StringMatchSpecification<T>(value, field));
            return this;
        }

        public ConditionBuilder<T> IFStringMatch(bool bFlag, string value, Expression<Func<T, string>> field)
        {
            if (bFlag)
                StringMatch(value, field);
            return this;
        }

        public ConditionBuilder<T> StringMatchPhrase(string value, Expression<Func<T, string>> field)
        {
            AddQuery(new StringMatchPhraseSpecification<T>(value, field));
            return this;
        }

        public ConditionBuilder<T> IFStringMatchPhrase(bool bFlag, string value, Expression<Func<T, string>> field)
        {
            if (bFlag)
                StringMatchPhrase(value, field);
            return this;
        }

        public ConditionBuilder<T> StringTerms(List<string> values, Expression<Func<T, string>> field)
        {
            AddQuery(new StringTermsSpecification<T>(values, field));
            return this;
        }

        public ConditionBuilder<T> IFStringTerms(bool bFlag, List<string> values, Expression<Func<T, string>> field)
        {
            if (bFlag)
                StringTerms(values, field);
            return this;
        }


        public ConditionBuilder<T> NullableDateRangeMatch(string beginTime, string endTime,
            Expression<Func<T, string>> field)
        {
            var beginTimeFlag = DateTime.TryParse(beginTime, out DateTime beginTimeValue);
            var endTimeFlag = DateTime.TryParse(endTime, out DateTime endTimeValue);
            if (beginTimeFlag || endTimeFlag)
            {
                AddQuery(new DateRangeSpecification<T>(
                    beginTimeFlag ? beginTimeValue : (DateTime?)null,
                    endTimeFlag ? endTimeValue : (DateTime?)null,
                    field));
            }

            return this;
        }

        public ConditionBuilder<T> NullableNumberRangeMatch(string beginNum, string endNum,
            Expression<Func<T, int>> field)
        {
            var numBeginFlag = int.TryParse(beginNum, out int numBeginValue);
            var numEndFlag = int.TryParse(endNum, out int numEndValue);
            if (numBeginFlag || numEndFlag)
            {
                AddQuery(new EsSearchNumRangeQuery<T>(
                    numBeginFlag ? numBeginValue : (int?)null,
                    numEndFlag ? numEndValue : (int?)null,
                    field));
            }

            return this;
        }

        public ConditionBuilder<T> Should(List<ISearchSpecification<ISearchRequest<T>>> specifications, int minimumShouldMatch = 1)
        {
            AddQuery(new ShouldBaseSpecification<T>(specifications, minimumShouldMatch));
            return this;
        }

        public ConditionBuilder<T> IFShould(bool bflag, List<ISearchSpecification<ISearchRequest<T>>> specifications, int minimumShouldMatch = 1)
        {
            if (bflag)
                Should(specifications, minimumShouldMatch);
            return this;
        }

        public ConditionBuilder<T> Should(Func<ConditionBuilder<T>, ConditionBuilder<T>> should, int minimumShouldMatch = 1)
        {
            Should(should.Invoke(new ConditionBuilder<T>()).Queries, minimumShouldMatch);
            return this;
        }

        public ConditionBuilder<T> IFShould(bool bflag, Func<ConditionBuilder<T>, ConditionBuilder<T>> should, int minimumShouldMatch = 1)
        {
            if (bflag)
            {
                Should(should, minimumShouldMatch);
            }

            return this;
        }

        public ConditionBuilder<T> Must(List<ISearchSpecification<ISearchRequest<T>>> specifications)
        {
            AddQuery(new MustBaseSpecification<T>(specifications));
            return this;
        }

        public ConditionBuilder<T> IFMust(bool bflag, List<ISearchSpecification<ISearchRequest<T>>> specifications)
        {
            if (bflag)
                Must(specifications);
            return this;
        }

        public ConditionBuilder<T> Must(Func<ConditionBuilder<T>, ConditionBuilder<T>> must)
        {
            Must(must.Invoke(new ConditionBuilder<T>()).Queries);
            return this;
        }

        public ConditionBuilder<T> IFMust(bool bflag, Func<ConditionBuilder<T>, ConditionBuilder<T>> must)
        {
            if (bflag)
            {
                Must(must);
            }

            return this;
        }

        public ConditionBuilder<T> HasChild<TC>(string childType, Func<ConditionContext<TC>, ConditionContext<TC>> hasChild)
            where TC : class
        {
            var childCondition = new ConditionContext<TC>();
            hasChild.Invoke(childCondition);
            AddQuery(new HasChildSpecification<T, TC>(childType,
                descriptor =>
                {
                    descriptor.Must(childCondition.MustBuilder.GetContainers().ToArray());
                    descriptor.MustNot(childCondition.MustNotBuilder.GetContainers().ToArray());
                    descriptor.Should(childCondition.ShouldBuilder.GetContainers().ToArray());
                    return descriptor;
                }
            ));
            return this;
        }

        public ConditionBuilder<T> IfHasChild<TC>(bool bFlag, string childType,
            Func<ConditionContext<TC>, ConditionContext<TC>> hasChild)
            where TC : class
        {
            if (bFlag)
                HasChild(childType, hasChild);
            return this;
        }

        public List<QueryContainer> GetContainers()
        {
            var queryContainer = new List<QueryContainer>();

            if (_hasConditions)
            {
                queryContainer.AddRange(_queries.Select(qu => qu
                    .IsSatisfiedBy()
                    .Query
                ));
            }

            return queryContainer;
        }
    }
}